// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <evmc/hex.hpp>
#include <gtest/gtest.h>
#include <intx/intx.hpp>
#include <test/state/precompiles_internal.hpp>
#include <test/utils/utils.hpp>

TEST(expmod, test_vectors)
{
    struct TestCase
    {
        std::string_view base_hex;
        std::string_view exp_hex;
        std::string_view mod_hex;
        std::string_view expected_result_hex;
    };

    /// Test vectors for expmod precompile.
    const std::vector<TestCase> test_cases{
        {"", "", "", ""},
        {"", "", "00", "00"},
        {"", "", "01", "00"},
        {"", "", "02", "01"},
        {"", "", "0200", "0001"},
        {"00", "00", "02", "01"},
        {"02", "03", "00", "00"},
        {"02", "01", "03", "02"},
        {"02", "03", "06", "02"},
        {"03", "", "06", "01"},
        {"03", "00", "06", "01"},
        {"03", "01", "14", "03"},
        {"03", "02", "14", "09"},
        {"03", "03", "03a0", "001b"},
        {"09", "05", "10", "09"},
        {"09", "05", "11", "08"},
        {"09", "05", "13", "10"},
        {"09", "05", "17", "08"},
        {"09", "05", "18", "09"},
        {"03", "80", "ff", "ab"},
        {"03", "1c93", "61", "5f"},
        {
            "03",
            "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e",
            "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
            "0000000000000000000000000000000000000000000000000000000000000001",
        },
        {
            "02",
            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
            "000000000000000000000000000000000000000000000000000000000000000000000000000000000003",
            "0006",
            "0002",
        },
        {
            "20000000000000000000000000000000000000000000000000000000000000000000000000000010200000"
            "00000000000000000000000000000000000000000000000000000000000000000000000010200000000000"
            "00000000000000000000000000000000000000000000000000000000000000000010200000000000000000"
            "00000000000000000000000000000000000000000000000000000000000010",
            "03",
            "60000000000000000000000000000000000000000000000000000000000000000000000000000010600000"
            "00000000000000000000000000000000000000000000000000000000000000000000000010600000000000"
            "00000000000000000000000000000000000000000000000000000000000000000010600000000000000000"
            "00000000000000000000000000000000000000000000000000000000000010",
            "291b1e2948112b098f3c987bec2dbac8022f5e4ebd3d4c47f333b30e3de21ca3b8aca475da7d3240291b1e"
            "2948112b098f3c987bec2dbac8022f5e4ebd3d4c47f333b30e3de21ca3b8aca475da7d3240291b1e294811"
            "2b098f3c987bec2dbac8022f5e4ebd3d4c47f333b30e3de21ca3b8aca475da7d3240291b1e2948112b098f"
            "3c987bec2dbac8022f5e4ebd3d4c47f333b30e3de21ca3b8aca475da7d3240",
        },
        {
            "20000000000000000000000000000000000000000000000000000000000000000000000000000010200000"
            "00000000000000000000000000000000000000000000000000000000000000000000000010200000000000"
            "00000000000000000000000000000000000000000000000000000000000000000010200000000000000000"
            "00000000000000000000000000000000000000000000000000000000000010200000000000000000000000"
            "00000000000000000000000000000000000000000000000000000010200000000000000000000000000000"
            "00000000000000000000000000000000000000000000000010200000000000000000000000000000000000"
            "00000000000000000000000000000000000000000010200000000000000000000000000000000000000000"
            "00000000000000000000000000000000000010",
            "03",
            "60000000000000000000000000000000000000000000000000000000000000000000000000000010600000"
            "00000000000000000000000000000000000000000000000000000000000000000000000010600000000000"
            "00000000000000000000000000000000000000000000000000000000000000000010600000000000000000"
            "00000000000000000000000000000000000000000000000000000000000010600000000000000000000000"
            "00000000000000000000000000000000000000000000000000000010600000000000000000000000000000"
            "00000000000000000000000000000000000000000000000010600000000000000000000000000000000000"
            "00000000000000000000000000000000000000000010600000000000000000000000000000000000000000"
            "00000000000000000000000000000000000010",
            "2d408bba3fa657dbb2cd49d4a1d329966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408b"
            "ba3fa657dbb2cd49d4a1d329966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa6"
            "57dbb2cd49d4a1d329966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa657dbb2"
            "cd49d4a1d329966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa657dbb2cd49d4"
            "a1d329966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa657dbb2cd49d4a1d329"
            "966c23e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa657dbb2cd49d4a1d329966c23"
            "e59e10c2a65950af0c4b047e185de46ee3d11f9b6b202d408bba3fa657dbb2cd49d4a1d329966c23e59e10"
            "c2a65950af0c4b047e185de46ee3d11f9b6b20",
        },
    };

    for (const auto& [base_hex, exp_hex, mod_hex, expected_result_hex] : test_cases)
    {
        const auto base = *evmc::from_hex(base_hex);
        const auto exp = *evmc::from_hex(exp_hex);
        const auto mod = *evmc::from_hex(mod_hex);
        const auto expected_result = *evmc::from_hex(expected_result_hex);

        evmc::bytes input(3 * 32, 0);
        using namespace intx;
        be::unsafe::store(&input[0], uint256{base.size()});
        be::unsafe::store(&input[32], uint256{exp.size()});
        be::unsafe::store(&input[64], uint256{mod.size()});
        input += base;
        input += exp;
        input += mod;

        evmc::bytes result(mod.size(), 0xfe);
        const auto [status, output_size] =
            evmone::state::expmod_execute(input.data(), input.size(), result.data(), result.size());
        EXPECT_EQ(status, EVMC_SUCCESS);
        EXPECT_EQ(output_size, expected_result.size());
        EXPECT_EQ(hex(result), expected_result_hex);
    }
}

TEST(expmod, analysis_oog)
{
    // Tests the gas cost calculation of the expmod precompile.
    // The result cost is expected to prohibit execution.
    static constexpr auto GAS_LIMIT = 1'000'000'000;
    static constexpr std::array inputs{
        // clang-format off
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 80000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 40000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 20000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 10000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 80000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 40000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 20000020 0000000000000000000000000000000000000000000000000000000000000001 80",
        "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001",
        "0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000040 00000000000000000000000000000000000000000000000000000000ffffffff 80"
        "0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000040 00000000000000000000000000000000000000000000000000000000ffffffff 80",
        "00000000000000000000000000000000000000000000000000000000ffffffff 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000ffffffff 0000000000000000000000000000000000000000000000000000000000000001 80",
        "0000000000000000000000000000000000000000000000000000000080000000 0000000000000000000000000000000000000000000000000000000080000000 0000000000000000000000000000000000000000000000000000000000000001 80",
        // clang-format on
    };

    for (const auto& input_hex : inputs)
    {
        const auto input = evmc::from_spaced_hex(input_hex).value();
        const auto [gas_cost, max_output_size] = evmone::state::expmod_analyze(input, EVMC_PRAGUE);
        EXPECT_GT(gas_cost, GAS_LIMIT);
    }
}

TEST(expmod, incomplete_inputs)
{
    struct TestCase
    {
        std::string_view input_hex;
        std::string_view expected_result_hex;
    };

    // Tests for expmod with raw and incomplete inputs (requires padding input with zero bytes).
    static constexpr auto GAS_LIMIT = 100'000'000;
    const std::string huge_output(0x20000 * 2, '0');
    const std::vector<TestCase> inputs{
        // clang-format off
        {"", ""},
        {"0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 02", "0001"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001", "00"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 ba ee", "00"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000002 ba ee d0", "9000"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000003 ba ee d000", "100000"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000003 ba ee d001", "789700"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000003 ba ee 00d0", "009000"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000003 ba ee 0000", "000000"},
        {"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000003 ba ee 000000 fe", "000000"},
        {"0000000000000000000000000000000000000000000000000000000000000010 0000000000000000000000000000000000000000000000000000000000000010 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000001", "00"},
        {"000000000000000000000000000000000000000000000000000000000000ffff 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 80", "00"},
        {"0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000000", ""},
        {"0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000002 0000000000000000000000000000000000000000000000000000000000000000 80", ""},
        {"0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000100000000 0000000000000000000000000000000000000000000000000000000000000000 80", ""},
        {"0000000000000000000000000000000000000000000000000000000000020000 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000020000 80", huge_output},
        // clang-format on
    };

    for (const auto& [input_hex, expected_result_hex] : inputs)
    {
        const auto input = evmc::from_spaced_hex(input_hex).value();
        const auto [gas_cost, max_output_size] = evmone::state::expmod_analyze(input, EVMC_PRAGUE);
        ASSERT_LT(gas_cost, GAS_LIMIT);
        auto output = std::make_unique_for_overwrite<uint8_t[]>(max_output_size);
        const auto [status, output_size] = evmone::state::expmod_execute(
            input.data(), input.size(), output.get(), max_output_size);
        EXPECT_EQ(status, EVMC_SUCCESS);
        const auto result_hex = evmc::hex({output.get(), output_size});
        EXPECT_EQ(result_hex, expected_result_hex);
    }
}
